#include <iostream>
#include <fstream>
#include <sstream>
#include <time.h>

#include "inc.h"

#define DEBUG 1
#define LOG(x) if (DEBUG) {x;}

#define RECORD 0
#define REC(x) if (RECORD) SYNCED(x)

#define COMBINEDLOG 0
#define LOGC(x) if (!DEBUG & COMBINEDLOG) {x;}

#define MATRIX_WIDTH 8

#define THREAD_NUM 1

std::ofstream fout;
int fileIndex = 0;
int recordCount = 0;
std::string filename_prefix = "/Users/sagnitude/log/record_";

pthread_mutex_t sum_mutex;

/**
 * Data Handler
 */
void applyPiecePlacementAtDirection(Board board, int x, int y, Faction f, Direction d, int count) {
    while (count != 0) {
        x += DX(d);
        y += DY(d);
        SET_FACTION_AT(board, x, y, f);
        count--;
    };
}

int getNext(Board board, int x, int y, Direction d) {
    if (!CAN_MOVE(MATRIX_OFF(x, y), d)) {
        return 0;
    }

    x += DX(d);
    y += DY(d);

    return GET_FACTION_AT(board, x, y);
}

int countDirectionalFlippable(Board board, int x, int y, Faction f, Direction d) {
    int count = 0;
    int state = 0;
    while (1) {
        state = getNext(board, x, y, d);

        if (state == 0) {
            return 0;
        } else if (state == f) {
            return count;
        }

        x += DX(d);
        y += DY(d);

        count++;
    };
}

int countFlippable(Board board, int x, int y, Faction f) {
    int count = countDirectionalFlippable(board, x, y, f, TOP);
    count += countDirectionalFlippable(board, x, y, f, DOWN);
    count += countDirectionalFlippable(board, x, y, f, LEFT);
    count += countDirectionalFlippable(board, x, y, f, RIGHT);
    count += countDirectionalFlippable(board, x, y, f, TOPLEFT);
    count += countDirectionalFlippable(board, x, y, f, TOPRIGHT);
    count += countDirectionalFlippable(board, x, y, f, DOWNLEFT);
    count += countDirectionalFlippable(board, x, y, f, DOWNRIGHT);
    return count;
}

int countAndListFlippable(Board board, int x, int y, Faction f, int *flippable) {
    int count = flippable[0] = countDirectionalFlippable(board, x, y, f, TOP);
    count += flippable[1] = countDirectionalFlippable(board, x, y, f, DOWN);
    count += flippable[2] = countDirectionalFlippable(board, x, y, f, LEFT);
    count += flippable[3] = countDirectionalFlippable(board, x, y, f, RIGHT);
    count += flippable[4] = countDirectionalFlippable(board, x, y, f, TOPLEFT);
    count += flippable[5] = countDirectionalFlippable(board, x, y, f, TOPRIGHT);
    count += flippable[6] = countDirectionalFlippable(board, x, y, f, DOWNLEFT);
    count += flippable[7] = countDirectionalFlippable(board, x, y, f, DOWNRIGHT);
    return count;
}

int randomGameSelectorBot(Game game, Faction f, int *cache) {
    int c = f == F1 ? game.c1 : game.c2;
    if (c > 28) c = 28;
    int next = nextInt(c); //1 to c
    int ptr = 0;
    for (int i = 7; i >= 0; i--) {
        for (int j = 7; j >= 0; j--) {
            int faction = GET_FACTION_AT(game.capable, i, j);
            if (faction & f) {
                cache[ptr++] = MATRIX_OFF(i, j);
            }
        }
    }
    if (ptr == 0) {
        return ~0;
    }
    int cap = cache[next - 1];
    LOG(
            std::cout << "Capable points: " << (c) << "; " << std::endl;
    )
    return cap;
}

/**
 * Debug
 */
void printBoard(Board board) {
    std::cout << std::endl;
    for (int i = 0; i < MATRIX_WIDTH; i++) {
        for (int j = 0; j < MATRIX_WIDTH; j++) {
            int f = GET_FACTION_AT(board, i, j);
            if (f == 0) {
                std::cout << "- ";
            } else {
                std::cout << f << " ";
            }
        }
        std::cout << std::endl;
    }
}

void printGame(Game game) {
    std::cout << std::endl;
    for (int i = 0; i < MATRIX_WIDTH; i++) {
        for (int j = 0; j < MATRIX_WIDTH; j++) {
            int c = GET_FACTION_AT(game.capable, i, j);
            int f = GET_FACTION_AT(game.board, i, j);
            if (f == 0) {
                if (c == 0) {
                    std::cout << "- ";
                } else {
                    std::cout << c << "`";
                }
            } else {
                std::cout << f;
                if (c == 0) {
                    std::cout << " ";
                } else {
                    std::cout << c;
                }
            }
        }
        std::cout << std::endl;
    }
}

int endGameCheck(Game game) {
    //board full?
    if (EMPTYSLOTS_ONBOARD(game.board) == 0) {
        LOG(
                std::cout << " board full, end game " << std::endl;
        )
        return 1;
    }
    //any faction empty?
    if (game.f1 == 0 || game.f2 == 0) {
        LOG(
                std::cout << " someone failed, end game " << std::endl;
        )
        return 1;
    }
    //no one has a capable point?
    if (game.c1 == 0 && game.c2 == 0) {
        LOG(
                std::cout << " no one has a proper point, end game " << std::endl;
        )
        return 1;
    }
    //game too long, force quit
    if (game.steps > 70) {
        LOG(
                std::cout << " game over 70 steps, game failed " << std::endl;
        )
        return 1;
    }
    LOG(
            std::cout << " game goes on " << std::endl;
    )
    return 0;
}

void endGame(int *records, int count, Faction f) {
    LOG(
            std::cout << "Game end: with " << count << " steps" << std::endl;
    )

    REC(
            recordCount++;
            if (recordCount == 100000) {
                recordCount = 0;
                fileIndex++;
                fout.close();
                std::stringstream ss;
                ss << filename_prefix << fileIndex << ".log";
                fout.open(ss.str());
            }
    )

    for (int i = 0; i < count; i++) {
        int b = records[i * 9 + 8] >> 24;
        records[i * 9 + 8] |= (b == f);

        REC(
                fout << records[i * 9 + 0] << "," <<
                        records[i * 9 + 1] << "," <<
                        records[i * 9 + 2] << "," <<
                        records[i * 9 + 3] << "," <<
                        records[i * 9 + 4] << "," <<
                        records[i * 9 + 5] << "," <<
                        records[i * 9 + 6] << "," <<
                        records[i * 9 + 7] << "," <<
                        records[i * 9 + 8] << std::endl;
        )
    }

}

int startGame(Game game, int (*p1)(Game, Faction, int *), int(*p2)(Game, Faction, int *)) {
    game.board[0] = 24;
    game.board[1] = 0;
    game.board[2] = 0;
    game.board[3] = 402653184;
    game.board[4] = 0;
    game.players[0] = p1;
    game.players[1] = p2;

    int x = 0;
    int y = 0;
    Faction currentFaction;
    int *flippables = (int *) malloc(8 * sizeof(int));
    int flippableCount = 0;
    int tempFaction;

    while (true) {
        game.step = (game.step + 1) % 2;
        game.c1 = game.c2 = 0;
        currentFaction = factions[game.step];

        /*
         * Calculate Inner Capable Matrix
         */
        for (int i = 0; i < MATRIX_WIDTH; i++) {
            for (int j = 0; j < MATRIX_WIDTH; j++) {
                tempFaction = GET_FACTION_AT(game.board, i, j);
                int f = None;
                if (tempFaction == 0) {
                    int c1 = countFlippable(game.board, i, j, F1) != 0;
                    int c2 = countFlippable(game.board, i, j, F2) != 0;
                    f = c1 + c2 * 2;
                    game.c1 += c1;
                    game.c2 += c2;
                }
                SET_FACTION_AT(game.capable, i, j, (Faction) f);
            }
        }

        LOG(
                std::cout << std::endl;
                std::cout << "Capable: (" << game.c1 << ", " << game.c2 << ");" << std::endl;
        )

        game.action = game.players[game.step](game, currentFaction, game.capableCache);

        x = game.action / MATRIX_WIDTH;
        y = game.action % MATRIX_WIDTH;

        /*
         * Generate record
         */
        game.records[game.steps * 9 + 0] = game.board[0];
        game.records[game.steps * 9 + 1] = game.board[1];
        game.records[game.steps * 9 + 2] = game.board[2];
        game.records[game.steps * 9 + 3] = game.board[3];
        game.records[game.steps * 9 + 4] = game.capable[0];
        game.records[game.steps * 9 + 5] = game.capable[1];
        game.records[game.steps * 9 + 6] = game.capable[2];
        game.records[game.steps * 9 + 7] = game.capable[3];
        int r = (currentFaction & 0xFF) << 24;

        LOG(
                std::cout << "Capable matrix: " << std::endl;
                printBoard(game.capable);
                std::cout << "Step " << game.steps << ", player " << " F" << (game.step + 1) << std::endl;
        )

        LOGC(
                std::cout << std::endl;
                printGame(game);
        )

        if (game.action != ~0) {
            r |= (x & 0xFF) << 16;
            r |= (y & 0xFF) << 8;
            game.records[game.steps * 9 + 8] = r;

            LOG(
                    std::cout << "at (" << x << ", " << y << ") with offset " << game.action << std::endl;
            )
        } else {
            r |= 0xFFFF00;
            game.records[game.steps * 9 + 8] = r;

            LOG(
                    std::cout << "pass" << std::endl;
            )
            goto check;
        }

        /*
         * Put a piece at (x, y)
         */
        flippableCount = countAndListFlippable(game.board, x, y, currentFaction, flippables);
        if (flippableCount == 0) {
            continue;
        }
        tempFaction = GET_FACTION_AT(game.board, x, y);
        if (tempFaction != 0) {
            continue;
        }
        SET_FACTION_AT(game.board, x, y, currentFaction);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, TOP,       flippables[0]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, DOWN,      flippables[1]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, LEFT,      flippables[2]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, RIGHT,     flippables[3]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, TOPLEFT,   flippables[4]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, TOPRIGHT,  flippables[5]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, DOWNLEFT,  flippables[6]);
        applyPiecePlacementAtDirection(game.board, x, y, currentFaction, DOWNRIGHT, flippables[7]);

        LOG(
                std::cout << std::endl;
                std::cout << "Game Matrix: " << std::endl;
                printBoard(game.board);
        )

        check:

        game.steps++;

        /*
         * End game check
         */
        game.f1 = FACTION_CNT(game.board, F1);
        game.f2 = FACTION_CNT(game.board, F2);
        if (endGameCheck(game) == 1) {
            endGame(game.records, game.steps, game.f1 > game.f2 ? F1 : game.f1 == game.f2 ? None : F2);

            LOGC(
                    std::cout << std::endl;
                    printGame(game);
            )
            break;
        }
    };
    free(flippables);
    return 1;
}

void *singleGame(void *args) {
    for (int i = 0; i < 1; i++) {
        LOG(
                std::cout << " running " << i << std::endl;
        )
        Game *g = (Game *) malloc(sizeof(struct Game));
        *g = Game();
        startGame(*g, randomGameSelectorBot, randomGameSelectorBot);
        free(g);
    }
    return args;
}

int main() {
    srand((unsigned int) time(NULL));

    std::stringstream ss;
    ss << filename_prefix << fileIndex << ".log";
    fout.open(ss.str());

    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    pthread_mutex_init(&sum_mutex, NULL);

    pthread_t tids[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; ++i) {
        int ret = pthread_create(&tids[i], NULL, singleGame, NULL);
        if (ret != 0) {
            LOG(
                    std::cout << "pthread_create error:error_code=" << ret << std::endl;
            )
        }
    }
    pthread_attr_destroy(&attr);

    void *status;
    for (int i = 0; i < THREAD_NUM; ++i) {
        int ret = pthread_join(tids[i], &status);
        if (ret != 0) {
            LOG(
                    std::cout << "pthread_join error:error_code=" << ret << std::endl;
            )
        }
    }

    pthread_mutex_destroy(&sum_mutex);

    pthread_exit(NULL);

    return 0;
}
